// Project: Salient
// http://salient.codeplex.com
// 
// Copyright 2010, Sky Sanders <sky at skysanders.net>
// Dual licensed under the MIT or GPL Version 2 licenses.
// http://salient.codeplex.com/license
// 
// Date: April 24 2010 

#region

using System;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;

#endregion

namespace Salient.ServiceModel
{
    /// <summary>
    /// This class contains utility methods related to safely invoking WCF services.
    /// To be used as an lternative to the using statement to handle exceptions thrown 
    /// by the Close method by calling the Abort method to ensure the transition to 
    /// the Closed state.
    /// </summary>
    public static class ServiceInvoker
    {
        #region Proxy Client Methods

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingProxy<TService>(Action<TService> action)
            where TService : class, IDisposable, new()
        {
            // create an instance of TService and invoke the action
            TService service = new TService();
            service.InvokeAction(action);
        }


        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <typeparam name="TClient">Proxy Client Type</typeparam>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingProxy<TClient, TService>(Action<TClient> action)
            where TService : class
            where TClient : ClientBase<TService>, new()
        {
            // create an instance of TService and invoke the action
            TClient service = new TClient();
            service.InvokeAction(action);
        }


        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointConfigurationName">The configuration name used for the endpoint.</param>
        /// <typeparam name="TClient">Proxy Client Type</typeparam>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingProxy<TClient, TService>(string endpointConfigurationName, Action<TClient> action)
            where TService : class
            where TClient : ClientBase<TService>, new()
        {
            // create an instance of TService and invoke the action
            ConstructorInfo ctor = typeof (TClient).GetConstructor(new[] {typeof (string)});
            TClient service = (TClient) ctor.Invoke(new object[] {endpointConfigurationName});

            service.InvokeAction(action);
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointConfigurationName">The configuration name used for the endpoint.</param>
        /// <param name="remoteAddress">The address that provides the location of the service.</param>
        /// <typeparam name="TClient">Proxy Client Type</typeparam>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingProxy<TClient, TService>(string endpointConfigurationName, string remoteAddress,
                                                         Action<TClient> action)
            where TService : class
            where TClient : ClientBase<TService>, new()
        {
            UsingProxy<TClient, TService>(endpointConfigurationName, new EndpointAddress(remoteAddress), action);
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointConfigurationName">The configuration name used for the endpoint.</param>
        /// <param name="remoteAddress">The EndpointAddress that provides the location of the service.</param>
        /// <typeparam name="TClient">Proxy Client Type</typeparam>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingProxy<TClient, TService>(string endpointConfigurationName, EndpointAddress remoteAddress,
                                                         Action<TClient> action)
            where TService : class
            where TClient : ClientBase<TService>, new()
        {
            ConstructorInfo ctor = typeof (TClient).GetConstructor(new[] {typeof (string), typeof (EndpointAddress)});
            TClient service = (TClient) ctor.Invoke(new object[] {endpointConfigurationName, remoteAddress});
            service.InvokeAction(action);
        }


        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="binding"></param>
        /// <param name="remoteAddress">The EndpointAddress that provides the location of the service.</param>
        /// <typeparam name="TClient">Proxy Client Type</typeparam>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingProxy<TClient, TService>(Binding binding, EndpointAddress remoteAddress,
                                                         Action<TClient> action)
            where TService : class
            where TClient : ClientBase<TService>, new()
        {
            ConstructorInfo ctor = typeof (TClient).GetConstructor(new[] {typeof (Binding), typeof (EndpointAddress)});
            TClient service = (TClient) ctor.Invoke(new object[] {binding, remoteAddress});
            service.InvokeAction(action);
        }

        #endregion

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointConfigurationName">The configuration name used for the endpoint.</param>
        /// <param name="remoteAddress">The address that provides the location of the service.</param>
        /// <typeparam name="TClient">Proxy Client Type</typeparam>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingSoapProxy<TClient, TService>(string endpointConfigurationName, string remoteAddress,
                                                             Action<TClient> action)
            where TService : class
            where TClient : ClientBase<TService>, new()
        {
            UsingSoapProxy<TClient, TService>(endpointConfigurationName, new EndpointAddress(remoteAddress), action);
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointConfigurationName">The configuration name used for the endpoint.</param>
        /// <param name="remoteAddress">The EndpointAddress that provides the location of the service.</param>
        /// <typeparam name="TClient">Proxy Client Type</typeparam>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingSoapProxy<TClient, TService>(string endpointConfigurationName,
                                                             EndpointAddress remoteAddress, Action<TClient> action)
            where TService : class
            where TClient : ClientBase<TService>, new()
        {
            ConstructorInfo ctor = typeof (TClient).GetConstructor(new[] {typeof (string), typeof (EndpointAddress)});
            TClient service = (TClient) ctor.Invoke(new object[] {endpointConfigurationName, remoteAddress});
            service.InvokeAction(action);
        }

        /// <summary>
        /// Safely invokes an action on a service then disposes the service channel.
        /// </summary>
        /// <typeparam name="TService">The service type.</typeparam>
        /// <param name="service">The service.</param>
        /// <param name="action">The action.</param>
        public static void InvokeAction<TService>(this TService service, Action<TService> action)
            where TService : class
        {
            try
            {
                action(service);
            }
            finally
            {
                ((ICommunicationObject)service).CloseOrAbort();
            }
        }

        /// <summary>
        /// Extends class to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="service">
        /// The service to Close or Abort then Dispose.
        /// </param>
        public static void CloseOrAbort(this ICommunicationObject service)
        {
            // do not throw null reference exception
            if (service == null)
            {
                return;
            }
            if (service.State != CommunicationState.Closed
                && service.State != CommunicationState.Closing)
            {
                try
                {
                    // if Close attempts closing the session on the server and there
                    // is a problem during the roundtrip it will throw an exception
                    // and never transition to the Closed state
                    service.Close();
                }
                catch (CommunicationException)
                {
                    // not closed - call Abort to transition to the closed state
                    service.Abort();
                }
                catch (TimeoutException)
                {
                    // not closed - call Abort to transition to the closed state
                    service.Abort();
                }
                catch (Exception)
                {
                    // not closed - call Abort to transition to the closed state
                    service.Abort();
                    // this is an unexpected exception type - throw
                    throw;
                }
            }
        }

        #region ChannelFactory Methods

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        ///
        /// This method will use the first matching endpoint found in configuration.
        /// To specify an endpoint use the appropriate overload.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(Action<TService> action)
            where TService : class
        {
            UsingFactory("*", action);
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointConfigurationName">The configuration name used for the endpoint.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(string endpointConfigurationName, Action<TService> action)
            where TService : class
        {
            ChannelFactory<TService> factory = new ChannelFactory<TService>(endpointConfigurationName);
            // create an instance of TService and invoke the action
            TService service = factory.CreateChannel();
            service.InvokeAction(action);
        }


        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="binding">The Binding specified for the channels produced by the factory.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(Binding binding, Action<TService> action)
            where TService : class
        {
            ChannelFactory<TService> factory = new ChannelFactory<TService>(binding);
            TService service = factory.CreateChannel();
            service.InvokeAction(action);
        }


        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpoint">The ServiceEndpoint for the channels produced by the factory.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(ServiceEndpoint endpoint, Action<TService> action)
            where TService : class
        {
            ChannelFactory<TService> factory = new ChannelFactory<TService>(endpoint);
            TService service = factory.CreateChannel();
            service.InvokeAction(action);
        }


        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="binding">The Binding used to configure the endpoint.</param>
        /// <param name="remoteAddress">The EndpointAddress that provides the location of the service.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(Binding binding, EndpointAddress remoteAddress,
                                                  Action<TService> action)
            where TService : class
        {
            ChannelFactory<TService> factory = new ChannelFactory<TService>(binding, remoteAddress);
            TService service = factory.CreateChannel();
            service.InvokeAction(action);
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="binding">The Binding used to configure the endpoint.</param>
        /// <param name="remoteAddress">The address that provides the location of the service.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(Binding binding, string remoteAddress, Action<TService> action)
            where TService : class
        {
            UsingFactory(binding, new EndpointAddress(remoteAddress), action);
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointConfigurationName">The configuration name used for the endpoint.</param>
        /// <param name="remoteAddress">The EndpointAddress that provides the location of the service.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(string endpointConfigurationName, EndpointAddress remoteAddress,
                                                  Action<TService> action)
            where TService : class
        {
            ChannelFactory<TService> factory = new ChannelFactory<TService>(endpointConfigurationName, remoteAddress);
            TService service = factory.CreateChannel();
            service.InvokeAction(action);
        }

        #endregion


    }
}